package cn.huermao.hio.other;

import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;

/**
 * 雪花算法，计算64bit的长整型数字（long 数据类型是 64 bit位，最大值: 9,223,372,036,854,775,807（2^63 -1））
 * == 1bit(空位) + 41bit时间戳 + 10bit工作机器id(workerId + dataCenterId) + 12bit序列号 ==
 *
 * @author 胡二毛
 */
public class KeyWorker {
    private static volatile KeyWorker keyWorker;
    // 数据中心(机房) id
    private byte dataCenterId;
    // 机器ID
    private byte workerId;
    // 同一时间的序列
    private long sequence = 0L;
    // 开始时间戳
    private long twepoch = 1650355040438L;
    // 数据中心ID所占的位数 5个bit 最大:11111(2进制)--> 31(10进制)
    private static long DATA_CENTER_ID_BITS = 5L;
    // 机器ID所占的位数 5个bit 最大:11111(2进制)--> 31(10进制)
    private static long WORKER_ID_BITS = 5L;
    // 5 bit最多只能有31个数字，就是说机器id最多只能是32以内
    private static long MAX_WORK_ID = -1L ^ (-1L << WORKER_ID_BITS);
    // 5 bit最多只能有31个数字，数据中心ID最多只能是32以内
    private static long MAX_DATA_CENTER_ID = -1L ^ (-1L << DATA_CENTER_ID_BITS);
    // 同一时间的序列所占的位数 12个bit 111111111111 = 4095  最多就是同一毫秒生成4096个
    private static long SEQUENCE_BITS = 12L;
    // 序列号掩码 4095 (0b111111111111=0xfff=4095)
    // 用于序号的与运算，保证序号最大值在0-4095之间
    private long sequenceMask = -1L ^ (-1L << SEQUENCE_BITS);
    // workerId的偏移量
    private long workerIdShift = SEQUENCE_BITS;
    // dataCenterId需要左移位数 12+5=17位
    private long dataCenterIdShift = SEQUENCE_BITS + WORKER_ID_BITS;
    // 时间戳需要左移位数 12+5+5=22位
    private long timestampLeftShift = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS;
    // 最近一次时间戳
    private long lastTimestamp = -1L;

    public static long getId() {
        if (keyWorker == null) {
            synchronized (KeyWorker.class) {
                if (keyWorker == null) {
                    keyWorker = new KeyWorker((byte) 1, (byte) 1);
                }
            }
        }
        return keyWorker.nextId();
    }

    private KeyWorker(byte dataCenterId, byte workerId) {
        // 合法判断
        if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) {
            throw new IllegalArgumentException(String.format("dataCenter Id can't be greater than %d or less than 0", MAX_DATA_CENTER_ID));
        }
        if (workerId > MAX_WORK_ID || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", MAX_WORK_ID));
        }
        this.dataCenterId = dataCenterId;
        this.workerId = workerId;
    }

    private static long getDataCenterId() {
        long id = 0L;
        try {
            InetAddress ip = InetAddress.getLocalHost();
            NetworkInterface network = NetworkInterface.getByInetAddress(ip);
            if (network == null) {
                id = 1L;
            } else {
                byte[] mac = network.getHardwareAddress();
                if (null != mac) {
                    id = (255L & (long) mac[mac.length - 1] | 65280L & (long) mac[mac.length - 2] << 8) >> 6;
//                    id %= maxDatacenterId + 1L;
                }
            }
        } catch (Exception var7) {
            System.err.printf(" getDatacenterId: %s \n" + var7.getMessage());
        }

        return id;
    }

    private static long getWorkerId() {
        //JAVA9以上版本
        //long pid = ProcessHandle.current().pid();
        String jvmName = ManagementFactory.getRuntimeMXBean().getName();
        return Long.valueOf(jvmName.split("@")[0]);
    }

    // 获取下一个随机的ID
    private synchronized long nextId() {
        // 获取当前时间戳，单位毫秒
        long timestamp = System.currentTimeMillis();
        if (timestamp < lastTimestamp) {
            System.err.printf("clock is moving backwards.  Rejecting requests until %d.", lastTimestamp);
            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds",
                    lastTimestamp - timestamp));
        }
        // 去重
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            // sequence序列大于4095
            if (sequence == 0) {
                // 调用到下一个时间戳的方法
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            // 如果是当前时间的第一次获取，那么就置为0
            sequence = 0;
        }
        // 记录上一次的时间戳
        lastTimestamp = timestamp;
        /*
         * (timestamp - twepoch) << timestampLeftShift) 表示将时间戳减去初始时间戳，再左移相应位数
         * (datacenterId << datacenterIdShift) 表示将数据id左移相应位数
         * (workerId << workerIdShift) 表示将工作id左移相应位数
         * | 是按位或运算符，例如：x | y，只有当x，y都为0的时候结果才为0，其它情况结果都为1。
         * 因为个部分只有相应位上的值有意义，其它位上都是0，所以将各部分的值进行 | 运算就能得到最终拼接好的id
         */
        return ((timestamp - twepoch) << timestampLeftShift) |
                (dataCenterId << dataCenterIdShift) |
                (workerId << workerIdShift) |
                sequence;
    }

    private long tilNextMillis(long lastTimestamp) {
        // 获取最新时间戳
        long timestamp = System.currentTimeMillis();
        // 如果发现最新的时间戳小于或者等于序列号已经超4095的那个时间戳
        while (timestamp <= lastTimestamp) {
            // 不符合则继续
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }
}
